package com.engine.salary.service.impl;

import cn.hutool.core.util.StrUtil;
import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.hrmelog.entity.dto.LoggerContext;
import com.engine.salary.common.LocalDateRange;
import com.engine.salary.config.SalaryElogConfig;
import com.engine.salary.entity.salaryacct.po.SalaryAcctRecordPO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctResultPO;
import com.engine.salary.entity.salaryitem.po.SalaryItemPO;
import com.engine.salary.entity.salarysob.po.SalarySobPO;
import com.engine.salary.entity.taxagent.param.TaxAgentQueryParam;
import com.engine.salary.entity.taxagent.po.TaxAgentPO;
import com.engine.salary.entity.taxdeclaration.bo.TaxDeclarationBO;
import com.engine.salary.entity.taxdeclaration.param.TaxDeclarationListQueryParam;
import com.engine.salary.entity.taxdeclaration.param.TaxDeclarationSaveParam;
import com.engine.salary.entity.taxdeclaration.po.TaxDeclarationPO;
import com.engine.salary.enums.OperateTypeEnum;
import com.engine.salary.enums.auth.AuthFilterTypeEnum;
import com.engine.salary.enums.salaryaccounting.SalaryAcctRecordStatusEnum;
import com.engine.salary.enums.salarysob.IncomeCategoryEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.datacollection.AddUpSituationMapper;
import com.engine.salary.mapper.salaryacct.SalaryAcctRecordMapper;
import com.engine.salary.mapper.taxdeclaration.TaxDeclarationMapper;
import com.engine.salary.service.*;
import com.engine.salary.service.auth.AuthService;
import com.engine.salary.service.auth.AuthServiceImpl;
import com.engine.salary.sys.service.SalarySysConfService;
import com.engine.salary.sys.service.impl.SalarySysConfServiceImpl;
import com.engine.salary.util.SalaryDateUtil;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.page.PageInfo;
import com.engine.salary.util.page.SalaryPageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import weaver.general.BaseBean;
import weaver.hrm.User;

import java.time.YearMonth;
import java.util.*;
import java.util.stream.Collectors;

import static com.engine.salary.sys.constant.SalarySysConstant.TAX_DECLARATION_DATE_TYPE;

@Slf4j
public class TaxDeclarationServiceImpl extends Service implements TaxDeclarationService {

    private final Boolean isLog = "true".equals(new BaseBean().getPropValue("hrmSalary", "log"));

    private TaxDeclarationMapper getTaxDeclarationMapper() {
        return MapperProxyFactory.getProxy(TaxDeclarationMapper.class);
    }

    private SalaryAcctRecordMapper getSalaryAcctRecordMapper() {
        return MapperProxyFactory.getProxy(SalaryAcctRecordMapper.class);
    }

    private AddUpSituationMapper getAddUpSituationMapper() {
        return MapperProxyFactory.getProxy(AddUpSituationMapper.class);
    }

    private TaxDeclarationDetailService getTaxDeclarationDetailService(User user) {
        return ServiceUtil.getService(TaxDeclarationDetailServiceImpl.class, user);
    }

    private AddUpSituationService getAddUpSituationService(User user) {
        return ServiceUtil.getService(AddUpSituationServiceImpl.class, user);
    }

    private TaxAgentService getTaxAgentService(User user) {
        return ServiceUtil.getService(TaxAgentServiceImpl.class, user);
    }

    private SalarySobService getSalarySobService(User user) {
        return ServiceUtil.getService(SalarySobServiceImpl.class, user);
    }

    private SalaryAcctResultService getSalaryAcctResultService(User user) {
        return ServiceUtil.getService(SalaryAcctResultServiceImpl.class, user);
    }

    private SalaryItemService getSalaryItemService(User user) {
        return ServiceUtil.getService(SalaryItemServiceImpl.class, user);
    }

    private SalaryAcctRecordService getSalaryAcctRecordService(User user) {
        return ServiceUtil.getService(SalaryAcctRecordServiceImpl.class, user);
    }

    private SalaryAcctEmployeeService getSalaryAcctEmployeeService(User user) {
        return ServiceUtil.getService(SalaryAcctEmployeeServiceImpl.class, user);
    }

    public AuthService getAuthService(User user) {
        return ServiceUtil.getService(AuthServiceImpl.class, user);
    }

    private SalarySysConfService getSalarySysConfService(User user) {
        return ServiceUtil.getService(SalarySysConfServiceImpl.class, user);
    }

    //是否根据税款所属期进行申报
    private final boolean isTaxDeclarationByTaxCycle = "1".equals(getSalarySysConfService(user).getValueByCode(TAX_DECLARATION_DATE_TYPE));

    @Override
    public List<TaxDeclarationPO> listByTaxCycleAndTaxAgentIds(YearMonth taxCycle, Collection<Long> taxAgentIds) {
        if (Objects.isNull(taxCycle) || CollectionUtils.isEmpty(taxAgentIds)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(84026, "参数错误"));
        }
        TaxDeclarationPO po = TaxDeclarationPO.builder().taxCycle(SalaryDateUtil.toDate(taxCycle, 1)).taxAgentIds(taxAgentIds).build();
        List<TaxDeclarationPO> taxDeclarationPOS = getTaxDeclarationMapper().listSome(po);
        return taxDeclarationPOS;
    }

    @Override
    public PageInfo<TaxDeclarationPO> listPageByParam(TaxDeclarationListQueryParam queryParam) {
        // 分页参数
        TaxDeclarationPO po = TaxDeclarationPO.builder().build();
        if (Objects.nonNull(queryParam.getFromSalaryMonth())) {
            po.setTaxCycleFromDate(queryParam.getFromSalaryMonth());
        }
        if (Objects.nonNull(queryParam.getEndSalaryMonth())) {
            po.setTaxCycleEndDate(queryParam.getEndSalaryMonth());
        }

        // 查询个税申报表
        List<TaxDeclarationPO> taxDeclarationPOS = getTaxDeclarationMapper().listSome(po);
        taxDeclarationPOS = getAuthService(user).auth(taxDeclarationPOS, AuthFilterTypeEnum.DATA_OPT, TaxDeclarationPO.class);

        if (StrUtil.isNotBlank(queryParam.getTaxAgentName())) {
            List<TaxAgentPO> taxAgentPOs = getTaxAgentService(user).list(TaxAgentQueryParam.builder().name(queryParam.getTaxAgentName()).build());
            Set<Long> taxAgentIds = SalaryEntityUtil.properties(taxAgentPOs, TaxAgentPO::getId);
            taxDeclarationPOS = taxDeclarationPOS.stream().filter(tax -> taxAgentIds.contains(tax.getTaxAgentId())).collect(Collectors.toList());
        }


        return SalaryPageUtil.buildPage(queryParam.getCurrent(), queryParam.getPageSize(),
                taxDeclarationPOS, TaxDeclarationPO.class);

    }

    //根据id查询taxAgents
    @Override
    public List<TaxAgentPO> countByTaxDeclarationId(Collection<Long> taxAgentIds) {
        if (CollectionUtils.isEmpty(taxAgentIds)) {
            return Collections.emptyList();
        }
        return getTaxAgentService(user).listByIds(taxAgentIds);
    }

    //根据id获取TaxDeclaration
    @Override
    public TaxDeclarationPO getById(Long id) {
        return getTaxDeclarationMapper().getById(id);
    }

    @Override
    public void save(TaxDeclarationSaveParam saveParam) {
        Long taxAgentId = saveParam.getTaxAgentId();

        if (taxAgentId == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(0, "请选择要申报的扣缴义务人！"));
        }

        // 个税扣缴义务人id
        Set<Long> taxAgentIds = Collections.singleton(taxAgentId);


        // 查询个税扣缴义务人
        List<TaxAgentPO> taxAgentPOS = getTaxAgentService(user).listByIds(taxAgentIds);
        Map<Long, String> taxAgentNameMap = SalaryEntityUtil.convert2Map(taxAgentPOS, TaxAgentPO::getId, TaxAgentPO::getName);
        List<SalaryAcctRecordPO> salaryAcctRecordPOS;
        Date taxCycle;
        //根据税款所属期申报
        if (isTaxDeclarationByTaxCycle) {
            taxCycle = saveParam.getTaxCycle();
            if (Objects.isNull(taxCycle)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(84026, "税款所属期参数错误"));
            }

            // 查询税款所属期个税扣缴义务人已经生成过的个税申报表
            List<TaxDeclarationPO> taxDeclarationPOS = listByTaxCycle(TaxDeclarationPO.builder().taxCycle(taxCycle).taxAgentIds(taxAgentNameMap.keySet()).build());
            // 已经生成过个税申报表，不允许再次生成个税申报表
            if (CollectionUtils.isNotEmpty(taxDeclarationPOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(107986, "{0}在{1}已经生成过个税申报表，不允许再次生成")
                        .replace("{0}", taxAgentNameMap.get(taxDeclarationPOS.get(0).getTaxAgentId()))
                        .replace("{1}", SalaryDateUtil.getFormatYearMonth(taxCycle)));
            }
            // 查询薪资所属月的薪资核算记录
            salaryAcctRecordPOS = listByTaxCycle(SalaryAcctRecordPO.builder().taxCycle(taxCycle).build());

        } else {
            //根据薪资所属月申报
            // 薪资所属月的日期范围
            LocalDateRange salaryMonthDateRange = SalaryDateUtil.localDate2Range(SalaryDateUtil.localDateToDate(saveParam.getSalaryMonth().atDay(1)));
            if (Objects.isNull(salaryMonthDateRange)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(84026, "薪资所属月参数错误"));
            }

            // 查询薪资所属月个税扣缴义务人已经生成过的个税申报表
            List<TaxDeclarationPO> taxDeclarationPOS = listBySalaryMonthTax(TaxDeclarationPO.builder().salaryMonths(salaryMonthDateRange).taxAgentIds(taxAgentNameMap.keySet()).build());
            // 已经生成过个税申报表，不允许再次生成个税申报表
            if (CollectionUtils.isNotEmpty(taxDeclarationPOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(107986, "{0}在{1}已经生成过个税申报表，不允许再次生成")
                        .replace("{0}", taxAgentNameMap.get(taxDeclarationPOS.get(0).getTaxAgentId()))
                        .replace("{1}", SalaryDateUtil.getFormatYearMonth(saveParam.getSalaryMonth())));
            }
            // 查询薪资所属月的薪资核算记录
            salaryAcctRecordPOS = listBySalaryMonth(SalaryAcctRecordPO.builder().salaryMonths(salaryMonthDateRange).build());
            List<SalarySobPO> salarySobPOS = getSalarySobService(user).listByTaxAgentId(saveParam.getTaxAgentId());
            if (CollectionUtils.isEmpty(salarySobPOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98874, "{0}该义务人下无账套数据").replace("{0}", saveParam.getSalaryMonth().toString()));
            }
            List<Long> salarySobIds = salarySobPOS.stream().map(SalarySobPO::getId).collect(Collectors.toList());

            salaryAcctRecordPOS.stream().filter(record -> salarySobIds.contains(record.getSalarySobId())).collect(Collectors.toList());

            // 无薪资核算记录，不允许生成个税申报表
            if (CollectionUtils.isEmpty(salaryAcctRecordPOS)) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98874, "{0}无申报数据").replace("{0}", saveParam.getSalaryMonth().toString()));
            }

            // 如果当前薪资所属月下存在不同的税款所属期，属于异常业务场景，不允许生成个税申报表
            taxCycle = salaryAcctRecordPOS.get(0).getTaxCycle();
            boolean differentTaxCycle = salaryAcctRecordPOS.stream().anyMatch(salaryAcctRecordPO -> salaryAcctRecordPO.getTaxCycle().compareTo(taxCycle) != 0);
            if (differentTaxCycle) {
                throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98876, "{0}存在不同的税款所属期，无法正常生成个税申报表，请调整账套设置，重新核算后再生成个税申报表")
                        .replace("{0}", SalaryDateUtil.getFormatYearMonth(saveParam.getSalaryMonth())));
            }
        }

        // 无薪资核算记录，不允许生成个税申报表
        if (CollectionUtils.isEmpty(salaryAcctRecordPOS)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(98874, "{0}无申报数据").replace("{0}", saveParam.getSalaryMonth().toString()));
        }

        // 查询薪资核算结果
        List<SalaryAcctResultPO> salaryAcctResultPOS = getSalaryAcctResultService(user)
                .listBySalaryAcctRecordIdsAndTaxAgentIds(SalaryEntityUtil.properties(salaryAcctRecordPOS, SalaryAcctRecordPO::getId), taxAgentIds);

        // 无薪资核算结果，不允许生成个税申报表
        if (CollectionUtils.isEmpty(salaryAcctResultPOS)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(110093, "{0}无可申报数据")
                    .replace("{0}", SalaryDateUtil.getFormatYearMonth(saveParam.getSalaryMonth())));
        }

        // 查询薪资账套
        Set<Long> salarySobIds = SalaryEntityUtil.properties(salaryAcctRecordPOS, SalaryAcctRecordPO::getSalarySobId);
        List<SalarySobPO> salarySobPOS = getSalarySobService(user).listByIds(salarySobIds);
        Map<Long, String> salarySobNameMap = SalaryEntityUtil.convert2Map(salarySobPOS, SalarySobPO::getId, SalarySobPO::getName);
        Set<Long> salaryAcctRecordIds = SalaryEntityUtil.properties(salaryAcctResultPOS, SalaryAcctResultPO::getSalaryAcctRecordId);
        salaryAcctRecordPOS = salaryAcctRecordPOS.stream().filter(salaryAcctRecordPO -> salaryAcctRecordIds.contains(salaryAcctRecordPO.getId())).collect(Collectors.toList());
        // 如果存在未归档的，也不允许生成个税申报表
        salaryAcctRecordPOS.forEach(salaryAcctRecordPO -> {
            if (Objects.equals(salaryAcctRecordPO.getStatus(), SalaryAcctRecordStatusEnum.NOT_ARCHIVED.getValue())){
                throw new SalaryRunTimeException(
                        String.format("%s%s账套有未归档数据，请全部归档后再申报"
                                ,SalaryDateUtil.getFormatYearMonth(saveParam.getSalaryMonth())
                                ,salarySobNameMap.getOrDefault(salaryAcctRecordPO.getSalarySobId(),"")
                        )
                );
            }
        });

        // 查询所有薪资项目
        List<SalaryItemPO> salaryItemPOS = getSalaryItemService(user).listAll();


        // 处理要保存的数据
        TaxDeclarationBO.Result result = TaxDeclarationBO.handle(saveParam, taxCycle, user, salaryItemPOS, salarySobPOS, salaryAcctResultPOS);
        // 保存个税申报表
        if (CollectionUtils.isNotEmpty(result.getNeedInsertTaxDeclarations())) {
            if (isLog) {
                log.info("salary TaxDeclaration step1 save {}", result.getNeedInsertTaxDeclarations().size());
            }
            getTaxDeclarationMapper().batchInsert(result.getNeedInsertTaxDeclarations());
        }
        // 保存个税申报表明细
        if (CollectionUtils.isNotEmpty(result.getNeedInsertTaxDeclarationDetails())) {
            if (isLog) {
                log.info("salary TaxDeclaration step2 detail save {}", result.getNeedInsertTaxDeclarationDetails().size());
            }
            getTaxDeclarationDetailService(user).batchSave(result.getNeedInsertTaxDeclarationDetails());
        }
        // 保存累计情况
        if (CollectionUtils.isNotEmpty(result.getNeedInsertAddUpSituations())) {
            if (isLog) {
                log.info("salary TaxDeclaration step3 AddUpSituations save {}", result.getNeedInsertAddUpSituations().size());
            }
            getAddUpSituationService(user).deleteByTaxYearMonthAndTaxAgentIds(SalaryDateUtil.localDate2YearMonth(taxCycle), taxAgentIds);
            getAddUpSituationService(user).batchSave((List) result.getNeedInsertAddUpSituations());
        }
        // 更新薪资核算记录的状态
        if (isLog) {
            log.info("salary TaxDeclaration step4 AcctRecordStatus save {}", salaryAcctRecordIds.size());
        }
        getSalaryAcctRecordService(user).updateStatusByIds(salaryAcctRecordIds, SalaryAcctRecordStatusEnum.DECLARED);

        // 记录日志
        result.getNeedInsertTaxDeclarations().stream().forEach(declare -> {
            String taxAgentName = taxAgentNameMap.getOrDefault(declare.getTaxAgentId(), "");
            String targetName = SalaryDateUtil.getFormatYearMonth(declare.getSalaryMonth()) + " " + taxAgentName + " " + IncomeCategoryEnum.parseByValue(declare.getIncomeCategory()).getDefaultLabel();
            LoggerContext<TaxDeclarationPO> loggerContext = new LoggerContext<>();
            loggerContext.setUser(user);
            loggerContext.setTargetId(declare.getId().toString());
            loggerContext.setTargetName(targetName);
            loggerContext.setOperateType(OperateTypeEnum.ADD.getValue());
            loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "生成个税申报表"));
            loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "生成个税申报表"));
            loggerContext.setNewValues(declare);
            SalaryElogConfig.taxDeclarationLoggerTemplate.write(loggerContext);
        });
    }

    @Override
    public void delete(SalaryAcctRecordPO salaryAcctRecordPO) {
        SalarySobPO sobPO = getSalarySobService(user).getById(salaryAcctRecordPO.getSalarySobId());
        if (sobPO == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(84026, "账套信息异常"));
        }

        // 薪资所属月的日期范围
        LocalDateRange taxCycleDateRange = SalaryDateUtil.localDate2Range(salaryAcctRecordPO.getTaxCycle());
        if (Objects.isNull(taxCycleDateRange)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(84026, "参数错误"));
        }
        sobPO.getTaxAgentIds().forEach(taxAgentId -> {
            List<TaxDeclarationPO> taxDeclarationPOS = listByTaxCycleAndTaxAgentIds(SalaryDateUtil.localDate2YearMonth(salaryAcctRecordPO.getTaxCycle()), Collections.singleton(taxAgentId));

            Set<Long> taxDeclarationIds = SalaryEntityUtil.properties(taxDeclarationPOS, TaxDeclarationPO::getId);
            if (CollectionUtils.isNotEmpty(taxDeclarationIds)) {
                // 删除个税申报表
                getTaxDeclarationMapper().deleteByIds(taxDeclarationIds);
                // 删除个税申报表详情
                getTaxDeclarationDetailService(user).deleteByTaxDeclarationIds(taxDeclarationIds);
            }

            // 删除往期累计情况
            getAddUpSituationService(user).deleteAddUpSituationList(salaryAcctRecordPO.getTaxCycle(), taxAgentId);
        });
    }


    public List<TaxDeclarationPO> listBySalaryMonthTax(TaxDeclarationPO build) {
        return getTaxDeclarationMapper().listSome(build);
    }

    public List<SalaryAcctRecordPO> listBySalaryMonth(SalaryAcctRecordPO po) {
        return getSalaryAcctRecordMapper().listSome(po);
    }

    public List<TaxDeclarationPO> listByTaxCycle(TaxDeclarationPO build) {
        return getTaxDeclarationMapper().listSome(build);
    }

    public List<SalaryAcctRecordPO> listByTaxCycle(SalaryAcctRecordPO po) {
        return getSalaryAcctRecordMapper().listSome(po);
    }

    @Override
    public boolean checkByAuthority(TaxDeclarationPO taxDeclarationPO, Long employeeId) {
        // 判断是否开启了分权
        Boolean openDevolution = getTaxAgentService(user).isOpenDevolution();
        // 获取所有个税扣缴义务人
        TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
        param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);

        Set<Long> taxAgentIds = SalaryEntityUtil.properties(taxAgentList, TaxAgentPO::getId);
        return taxAgentIds.contains(taxDeclarationPO.getTaxAgentId());
    }

    @Override
    public void withDrawTaxDeclaration(Long taxDeclarationId) {
        TaxDeclarationPO po = getTaxDeclarationMapper().getById(taxDeclarationId);
        if (Objects.isNull(po)) {
            throw new SalaryRunTimeException("个税申报表不存在");
        }
        // 获取当前个税扣缴义务人下的薪资账套
        List<SalarySobPO> salarySobPOS = getSalarySobService(user).listByTaxAgentId(po.getTaxAgentId());
        List<Long> salarySobIds = salarySobPOS.stream().map(SalarySobPO::getId).collect(Collectors.toList());
        // 获取记录
        LocalDateRange dateRange = new LocalDateRange(po.getSalaryMonth(), po.getSalaryMonth());
        List<SalaryAcctRecordPO> salaryAcctRecords = getSalaryAcctRecordService(user).listBySalarySobIdsAndSalaryMonth(salarySobIds, dateRange);
        List<Long> salaryAcctRecordIds = salaryAcctRecords.stream().map(SalaryAcctRecordPO::getId).collect(Collectors.toList());
        // 删除个税申报表
        getTaxDeclarationMapper().deleteByIdZj(po.getId());
        // 修改薪资核算记录状态为已归档
        if (CollectionUtils.isNotEmpty(salaryAcctRecordIds)) {
            getSalaryAcctRecordService(user).updateStatusByIds(salaryAcctRecordIds, SalaryAcctRecordStatusEnum.ARCHIVED);
        }

        // 查询个税扣缴义务人名称
        String bar = "_";
        TaxAgentPO taxAgentPO = getTaxAgentService(user).getById(po.getTaxAgentId());
        String targetName = SalaryDateUtil.getFormatYearMonth(po.getSalaryMonth()) + bar + taxAgentPO.getName() + bar + IncomeCategoryEnum.parseByValue(po.getIncomeCategory()).getDefaultLabel();
        // 记录日志
        LoggerContext<TaxDeclarationPO> loggerContext = new LoggerContext<>();
        loggerContext.setUser(user);
        loggerContext.setTargetId(taxDeclarationId.toString());
        loggerContext.setTargetName(targetName);
        loggerContext.setOperateType(OperateTypeEnum.DELETE.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "撤回个税申报表"));
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "撤回个税申报表"));
        SalaryElogConfig.taxDeclarationLoggerTemplate.write(loggerContext);
    }
}
